package com.sojson.util.pay.wx;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import javax.imageio.ImageIO;

import com.alibaba.fastjson.JSONObject;
import com.ijpay.core.kit.PayKit;
import com.sojson.config.exception.TransErrorCode;
import com.sojson.config.exception.TransException;
import com.sojson.constant.Constant;
import com.sojson.constant.IpConfig;
import com.sojson.result.bean.Status;
import com.sojson.util.StringUtil;
import com.sojson.util.SystemParamBaseUtil;
import com.sojson.util.bean.OtherUser;
import com.sojson.util.bean.PayOrder;
import com.sojson.util.cache.CacheUtil;
import com.sojson.util.encryption.Hmacsha256Util;
import com.sojson.util.encryption.Md5Util;
import com.sojson.util.encryption.Sha1Util;
import com.sojson.util.encryption.enums.EEncryptionType;
import com.sojson.util.file.FileUtil;
import com.sojson.util.httpclient.HttpClientUtil;
import com.sojson.util.id.GUIDUtil;
import com.sojson.util.json.JsonUtil;
import com.sojson.util.pay.wx.bean.WxParam;
import com.sojson.util.pay.wx.bean.WxResponse;
import com.sojson.util.pay.wx.bean.WxResult;
import com.sojson.util.pay.wx.bean.child.param.WxPamValue;
import com.sojson.util.pay.wx.constant.WxConstant;
import com.sojson.util.pay.wx.enums.EWxApiType;
import com.sojson.util.properties.PropertiesUtil;
import com.sojson.util.text.Convert;
import com.sojson.util.token.TokenUtil;

/**
 * 微信工具类
 * 
 * @author liu
 * @date 2020-11-02
 */
public class WxUtil {

    /** ------------------------------R:必填----------------------------- */
    /** ------------------------------C:一定条件下可选--------------------- */
    /** ------------------------------O:可选----------------------------- */

    /** ----------------------------以下是证书私钥---------------------------- */
    /** ----------------------------以下是证书私钥---------------------------- */
    /** ----------------------------以下是证书私钥---------------------------- */
    /** 微信商户私钥 */
    public static PrivateKey PRIVATE_KEY = getPrivateKey();
    /** ----------------------------以下是静态属性---------------------------- */
    /** ----------------------------以下是静态属性---------------------------- */
    /** ----------------------------以下是静态属性---------------------------- */
    /** 微信公众平台的token */
    public static final String TOKEN = Convert.toStr(Constant.PROPERTIES.getProperty("so.wx.gz.token"), "test");
    /** AccessToken在缓存中的前缀 */
    public static final String ACCESS_TOKEN_CACHE_PREFIX =
        Constant.PROPERTIES.getProperty("so.cache.wx.accessToken.name.prefix");
    /** AccessToken在缓存中的超时时间(以秒为单位),不设置为两小时 */
    public static final int ACCESS_TOKEN_CACHE_TIMEOUT = Convert.toInt(
        PropertiesUtil.getNumberFormat(Constant.PROPERTIES.getProperty("so.cache.wx.accessToken.timeout")),
        60 * 60 * 2);
    /** App的AccessToken在缓存中的前缀 */
    public static final String APP_ACCESS_TOKEN_CACHE_PREFIX =
        Constant.PROPERTIES.getProperty("so.cache.wx.app.accessToken.name.prefix");
    /** App的AccessToken在缓存中的超时时间(以秒为单位),不设置为两小时 */
    public static final int APP_ACCESS_TOKEN_CACHE_TIMEOUT =
        Convert.toInt(Constant.PROPERTIES.getProperty("so.cache.wx.app.accessToken.timeout"), 60 * 60 * 2);
    /** JsapiTicket在缓存中的前缀 */
    public static final String JSAPI_TICKET_CACHE_PREFIX =
        Constant.PROPERTIES.getProperty("so.cache.wx.jsapiTicket.name.prefix");
    /** JsapiTicket在缓存中的超时时间(以秒为单位),不设置为两小时 */
    public static final int JSAPI_TICKET_CACHE_TIMEOUT = Convert.toInt(
        PropertiesUtil.getNumberFormat(Constant.PROPERTIES.getProperty("so.cache.wx.jsapiTicket.timeout")),
        60 * 60 * 2);
    /** 生成密钥的随机字符串 */
    private static final String NONCE_STR_VALUE = "nonceStr";
    /** 生成签名的参数 */
    public static final String SIGNATURE_PARAM = Constant.PROPERTIES.getProperty("so.wx.signatureParam");

    /** ----------------------------以下是微信用户帐号---------------------------- */
    /** ----------------------------以下是微信用户帐号---------------------------- */
    /** ----------------------------以下是微信用户帐号---------------------------- */
    /** 类似帐号 */
    public static final String WEB_APPID = Constant.PROPERTIES.getProperty("so.wx.kf.appid");
    /** 类似密码 */
    public static final String WEB_APPSECRET = Constant.PROPERTIES.getProperty("so.wx.kf.appsecret");
    /** 类似帐号 */
    public static final String MOBILE_APPID = Constant.PROPERTIES.getProperty("so.wx.gz.appid");
    /** 类似密码 */
    public static final String MOBILE_APPSECRET = Constant.PROPERTIES.getProperty("so.wx.gz.appsecret");
    /** 微信商户号 */
    public static final String MCHID = Constant.PROPERTIES.getProperty("so.wx.mchid");
    /** 微信商户号密钥 */
    public static final String MCH_KEY = Constant.PROPERTIES.getProperty("so.wx.mchKey");
    /** 参数: key */
    public static final String FIELD_KEY = "key";
    /** 微信商户号密钥(作为参数时的字符串,为了减少拼接次数) */
    public static final String FIELD_MCH_KEY_PARAM = FIELD_KEY + "=";

    /** ----------------------------以下是微信接口返回的参数---------------------------- */
    /** ----------------------------以下是微信接口返回的参数---------------------------- */
    /** ----------------------------以下是微信接口返回的参数---------------------------- */
    // =======================================================================================================
    // =================================================用户信息================================================
    // =======================================================================================================
    /** 参数: appid(公众账号ID) */
    public static final String FIELD_APPID = "appid";
    /** 参数: openid(用户唯一标识) */
    public static final String FIELD_FIELD_OPENID = "openid";
    /** 参数: unionid((用户的唯一标识,多平台的唯一标识(WEB,移动...))) */
    public static final String FIELD_UNIONID = "unionid";
    /** 参数: nickname(昵称) */
    public static final String FIELD_NICKNAME = "nickname";
    /** 参数: subscribe(此用户是否关注公众号) */
    public static final String FIELD_SUBSCRIBE = "subscribe";
    /** 参数: sex(性别) */
    public static final String FIELD_SEX = "sex";
    /** 参数: province(省) */
    public static final String FIELD_PROVINCE = "province";
    /** 参数: city(市) */
    public static final String FIELD_CITY = "city";
    /** 参数: country(国家) */
    public static final String FIELD_COUNTRY = "country";
    /** 参数: headimgurl(头像) */
    public static final String FIELD_HEADIMGURL = "headimgurl";
    /** 参数: is_subscribe(是否关注公众账号) */
    public static final String FIELD_IS_SUBSCRIBE = "is_subscribe";
    /** 值-是否关注公众账号: 是 */
    public static final String FIELD_IS_SUBSCRIBE_Y = "Y";
    /** 值-是否关注公众账号: 否 */
    public static final String FIELD_IS_SUBSCRIBE_N = "N";
    // =======================================================================================================
    // =======================================================================================================
    // =======================================================================================================
    /** 参数: access_token(用户授权的唯一票据,用于调用微信的开放接口) */
    public static final String FIELD_ACCESS_TOKEN = "access_token";
    /** 参数: ticket(微信调用JS接口的临时票据) */
    public static final String FIELD_TICKET = "ticket";
    // =======================================================================================================
    // =================================================支付接口================================================
    // =======================================================================================================
    /** 参数: mch_id(商户号) */
    public static final String FIELD_MCH_ID = "mch_id";
    /** 参数: mch_id(商户密钥) */
    public static final String FIELD_MCH_KEY = "mch_key";
    /** 参数: device_info(设备号) */
    public static final String FIELD_DEVICE_INFO = "device_info";
    /** 参数: sign(签名) */
    public static final String FIELD_SIGN = "sign";
    /** 参数: sign_type(签名类型) */
    public static final String FIELD_SIGN_TYPE = "sign_type";
    /** 参数: body(商品描述) */
    public static final String FIELD_BODY = "body";
    /** 参数: detail(商品详情(必须是Json格式,单品优惠活动该字段必传,且必须按照规范上传)) */
    public static final String FIELD_DETAIL = "detail";
    /** 参数: notify_url(微信支付通知地址) */
    public static final String FIELD_NOTIFY_URL = "notify_url";
    /** 参数: nonce_str(生成密钥的随机字符串) */
    public static final String FIELD_NONCE_STR = "nonce_str";
    /** 参数: attach(附加数据,用来传自己需要的东西) */
    public static final String FIELD_ATTACH = "attach";
    /** 参数: out_trade_no(商户支付单号: 32个字符内，只能是数字、大小写字母_-|* 且在同一个商户号下唯一) */
    public static final String FIELD_OUT_TRADE_NO = "out_trade_no";
    /** 参数: total_fee(金额,单位: 分(必须是整数)) */
    public static final String FIELD_TOTAL_FEE = "total_fee";
    /** 参数: spbill_create_ip(终端IP) */
    public static final String FIELD_SPBILL_CREATE_IP = "spbill_create_ip";
    /** 参数: trade_type(交易类型,不同的类型决定了调起支付的方式,JSAPI: JSAPI支付,NATIVE: Native支付,APP: APP支付,MWEB: H5支付) */
    public static final String FIELD_TRADE_TYPE = "trade_type";
    /** 值-交易类型: JSAPI支付 */
    public static final String FIELD_TRADE_TYPE_VALUE_JSAPI = "JSAPI";
    /** 值-交易类型: NATIVE支付 */
    public static final String FIELD_TRADE_TYPE_VALUE_NATIVE = "NATIVE";
    /** 值-交易类型: APP支付 */
    public static final String FIELD_TRADE_TYPE_VALUE_APP = "APP";
    /** 值-交易类型: MWEB支付 */
    public static final String FIELD_TRADE_TYPE_VALUE_MWEB = "MWEB";
    /** 参数: product_id(商品ID,交易类型为NATIVE时，此参数必传) */
    public static final String FIELD_PRODUCT_ID = "product_id";
    /** 参数: prepay_id(预支付会话标识,用于后续接口调用中使用,该值有效期为2小时) */
    public static final String FIELD_PREPAY_ID = "prepay_id";
    /** 参数: code_url(二维码链接,trade_type=NATIVE时有返回,此url用于生成支付二维码，然后提供给用户进行扫码支付) */
    public static final String FIELD_CODE_URL = "code_url";
    /** 参数: time_stamp(系统当前时间戳) */
    public static final String FIELD_TIME_STAMP = "time_stamp";
    // =======================================================================================================
    // ===============================================公众号二维码===============================================
    // =======================================================================================================
    /** 参数: action_name(二维码类型) */
    public static final String FIELD_ACTION_NAME = "action_name";
    /** 值-二维码类型: 临时的整型参数值 */
    public static final String FIELD_ACTION_NAME_VALUE_SCENE = "QR_SCENE";
    /** 值-二维码类型: 临时的字符串参数值 */
    public static final String FIELD_ACTION_NAME_VALUE_STR_SCENE = "QR_STR_SCENE";
    /** 值-二维码类型: 永久的整型参数值 */
    public static final String FIELD_ACTION_NAME_VALUE_LIMIT_SCENE = "QR_LIMIT_SCENE";
    /** 值-二维码类型: 永久的字符串参数值 */
    public static final String FIELD_ACTION_NAME_VALUE_LIMIT_STR_SCENE = "QR_LIMIT_STR_SCENE";
    /** 参数: expire_seconds(临时二维码的有效时间,临时的二维码需要的参数) */
    public static final String FIELD_EXPIRE_SECONDS = "expire_seconds";
    /** 参数: action_info(二维码详细信息) */
    public static final String FIELD_ACTION_INFO = "action_info";
    /** 参数: scene */
    public static final String FIELD_SCENE = "scene";
    /** 参数: scene_id(场景值ID: 整型类型的内容) */
    public static final String FIELD_SCENE_ID = "scene_id";
    /** 参数: scene_str(场景值ID: 字符串类型的内容) */
    public static final String FIELD_SCENE_STR = "scene_str";
    // =======================================================================================================
    // =================================================状态码=================================================
    // =======================================================================================================
    /** 参数: errcode */
    public static final String FIELD_ERRCODE = "errcode";
    /** 参数: errmsg */
    public static final String FIELD_ERRMSG = "errmsg";
    /** 参数: return_code(XML格式) */
    public static final String FIELD_RETURN_CODE = "return_code";
    /** 参数: return_msg(XML格式) */
    public static final String FIELD_RETURN_MSG = "return_msg";
    /** 参数: result_code(XML格式) */
    public static final String FIELD_RESULT_CODE = "result_code";
    /** 参数: err_code(XML格式) */
    public static final String FIELD_ERR_CODE = "err_code";
    /** 参数: err_code_des(XML格式) */
    public static final String FIELD_ERR_CODE_DES = "err_code_des";

    /** ----------------------------以下是微信访问地址---------------------------- */
    /** ----------------------------以下是微信访问地址---------------------------- */
    /** ----------------------------以下是微信访问地址---------------------------- */
    /** 获取WEB端微信认证CODE的路径 */
    public static final String WEB_CODE_URL =
        String.format(Convert.toStr(Constant.PROPERTIES.getProperty("so.wx.kf.codeUrl"), ""), WEB_APPID, "%s", "%s");
    /** 获取WEB端用户认证AccessToken的路径 */
    public static final String WEB_USER_ACCESS_TOKEN_URL = String.format(
        Convert.toStr(Constant.PROPERTIES.getProperty("so.wx.userAccessTokenUrl"), ""), WEB_APPID, WEB_APPSECRET, "%s");
    /** 获取移动端微信认证CODE的路径 */
    public static final String MOBILE_CODE_URL =
        String.format(Convert.toStr(Constant.PROPERTIES.getProperty("so.wx.gz.codeUrl"), ""), MOBILE_APPID, "%s", "%s");
    /** 获取移动端用户认证AccessToken的路径 */
    public static final String MOBILE_USER_ACCESS_TOKEN_URL =
        String.format(Convert.toStr(Constant.PROPERTIES.getProperty("so.wx.userAccessTokenUrl"), ""), MOBILE_APPID,
            MOBILE_APPSECRET, "%s");
    /** 获取微信用户信息的路径,这里的access_token要通过微信认证接口返回的数据获取,和基础的AccessToken不一样 */
    public static final String USER_INFO_URL = Constant.PROPERTIES.getProperty("so.wx.userInfoUrl");
    /** 获取AccessToken的路径 */
    public static final String ACCESS_TOKEN_URL = String.format(
        Convert.toStr(Constant.PROPERTIES.getProperty("so.wx.accessTokenUrl"), ""), WEB_APPID, MOBILE_APPSECRET);
    /** 获取JsapiTicket的路径 */
    public static final String JSAPI_TICKET_URL = Constant.PROPERTIES.getProperty("so.wx.jsapiTicketUrl");
    /** 获取二维码ticket的路径 */
    public static final String QRCODE_TICKET = Constant.PROPERTIES.getProperty("so.wx.qrcode.ticket");
    /** 获取二维码的路径 */
    // public static final String QRCODE = Constant.PROPERTIES.getProperty("so.wx.qrcode");
    /** 微信支付二维码的内容 */
    public static final String BIZPAYURL = Constant.PROPERTIES.getProperty("so.wx.bizpayurl");
    /** 微信统一支付地址(通过不同的参数调起不同的支付页面) */
    public static final String UNIFIEDORDER = Constant.PROPERTIES.getProperty("so.wx.unifiedorder");
    /** 微信支付成功后的异步通知 */
    public static final String PAY_CALL_BACK = new StringBuilder(IpConfig.url).append("/pay")
        .append(Constant.PROPERTIES.getProperty("so.controller.mapping.open.other.wxPayCallBackSuccess")).toString();
    /** 微信支付成功后的异步通知地址 */
    public static final String ENCODE_PAY_CALL_BACK = encodeUrl(PAY_CALL_BACK);
    /** 小程序登录凭证校验 */
    public static final String XCX_LOGIN_TOKEN_VERIFY =
        String.format(Convert.toStr(Constant.PROPERTIES.getProperty("so.wx.xcx.loginTokenVerify"), ""), MOBILE_APPID,
            MOBILE_APPSECRET, "%s");

    /**
     * 获取微信AccessToken
     * 
     * @return
     * @throws Exception
     */
    public static WxResult getAccessToken() throws Exception {
        WxParam wxPar = new WxParam();
        wxPar.setAppid(getDefaultAppId());
        wxPar.setSecret(getDefaultAppsecret());

        return getAccessToken(wxPar);
    }

    /**
     * 获取微信AccessToken
     * 
     * @return
     * @throws Exception
     */
    public static WxResult getAccessToken(WxParam wx) throws Exception {
        String accessTokenCacheKey = getAccessTokenCacheKey(wx);
        WxResult wxResult = new WxResult();
        // 先从缓存中获取
        String accessToken = get(accessTokenCacheKey, String.class);
        if (isNotBlankObject(accessToken)) {
            Status status = new Status();
            wxResult.setStatus(status);
            status.setTransErrorCode(TransErrorCode.SUCCESS);
            wxResult.setAccessToken(accessToken);
            return wxResult;
        }

        // 从微信获取
        String doGet = doGet(String.format(EWxApiType.GET_ACCESS_TOKEN.getType(), wx.getAppid(), wx.getSecret()));

        WxResponse jsonToObject = jsonToObject(doGet);
        if (jsonToObject.getCusStatus().getCode() != TransErrorCode.SUCCESS.getCode()) {
            throw new TransException("获取微信AccessToken失败: " + jsonToObject.getErrmsg());
        }

        // 获取成功就保存到缓存
        setexYes(accessTokenCacheKey, jsonToObject.getAccessToken(), ACCESS_TOKEN_CACHE_TIMEOUT);

        wxResult.setAccessToken(jsonToObject.getAccessToken());
        return wxResult;
    }

    /**
     * 获取微信AccessToken
     * 
     * @return
     * @throws Exception
     */
    public static WxResult getAppAccessToken(String code) throws Exception {
        String appAppId = getDefaultAppId();
        String accessTokenCacheKey = getAppAccessTokenCacheKey(appAppId);
        // 先从缓存中获取
        WxResult wxResult = get(accessTokenCacheKey, WxResult.class);
        if (isNotBlankObject(wxResult)) {
            return wxResult;
        }

        // 从微信获取
        String doGet = doGet(
            String.format(EWxApiType.APP_GET_ACCESS_TOKEN_AND_OPENID.getType(), appAppId, getDefaultAppsecret(), code));

        WxResponse jsonToObject = jsonToObject(doGet);
        Status cusStatus = jsonToObject.getCusStatus();
        if (cusStatus.getCode() != TransErrorCode.SUCCESS.getCode()) {
            throw new TransException("获取微信App的AccessToken失败: " + cusStatus.getMsg());
        }

        wxResult = new WxResult(cusStatus);
        wxResult.setAccessToken(jsonToObject.getAccessToken());
        wxResult.setOpenid(jsonToObject.getOpenid());
        // 获取成功就保存到缓存
        setexYes(accessTokenCacheKey, wxResult, APP_ACCESS_TOKEN_CACHE_TIMEOUT);
        return wxResult;
    }

    /**
     * 获取AccessToken在缓存中的Key
     * 
     * @param wx
     * @return
     */
    private static String getAccessTokenCacheKey(WxParam wx) {
        return ACCESS_TOKEN_CACHE_PREFIX + wx.getAppid();
    }

    /**
     * 获取AccessToken在缓存中的Key
     * 
     * @param appid
     * @return
     */
    private static String getAppAccessTokenCacheKey(String appid) {
        return APP_ACCESS_TOKEN_CACHE_PREFIX + appid;
    }

    /**
     * 获取WEB端微信用户认证AccessToken
     * 
     * @param code 微信用户登录微信平台后,微信平台返回给回调接口的code
     * @return
     * @throws Exception
     */
    public static WxResult getWebUserAccessToken(String code) throws Exception {
        return getUserAccessToken(WEB_USER_ACCESS_TOKEN_URL, code);
    }

    /**
     * 获取移动端微信用户认证AccessToken
     * 
     * @param code 微信用户登录微信平台后,微信平台返回给回调接口的code
     * @return
     * @throws Exception
     */
    public static WxResult getMobileUserAccessToken(String code) throws Exception {
        return getUserAccessToken(MOBILE_USER_ACCESS_TOKEN_URL, code);
    }

    /**
     * 获取微信用户认证AccessToken
     * 
     * @param url 获取路径
     * @param code 微信用户登录微信平台后,微信平台返回给回调接口的code
     * @return
     * @throws Exception
     */
    private static WxResult getUserAccessToken(String url, String code) throws Exception {
        String urlResult = doGet(String.format(url, code));
        if (isBlankObject(urlResult)) {
            return new WxResult(TransErrorCode.OTHER_ERROR.getCode(), "通过CODE获取微信用户认证AccessToken,路径没有返回任何值!");
        }

        JSONObject jsonObject = jsonToObject(urlResult, JSONObject.class);
        Integer errorCode = jsonObject.getInteger(FIELD_ERRCODE);
        if (isNotBlankObject(errorCode)) {
            return new WxResult(errorCode, jsonObject.getString(FIELD_ERRMSG));
        }

        WxResult wxResult = new WxResult(TransErrorCode.SUCCESS.getCode());
        wxResult.setAccessToken(jsonObject.getString(FIELD_ACCESS_TOKEN));
        wxResult.setOpenid(jsonObject.getString(FIELD_FIELD_OPENID));
        return wxResult;
    }

    /**
     * 获取WEB端微信用户的信息
     * 
     * @param code 微信用户登录微信平台后,微信平台返回给回调接口的code
     * @return
     * @throws Exception
     */
    public static OtherUser getWebUserMessage(String code) throws Exception {
        WxResult authorization = getWebUserAccessToken(code);

        Status status = authorization.getStatus();
        if (status.getCode() == TransErrorCode.SUCCESS.getCode()) {
            return getUserMessage(authorization);
        }
        return new OtherUser(status);
    }

    /**
     * 获取移动端微信用户的信息
     * 
     * @param code 微信用户登录微信平台后,微信平台返回给回调接口的code
     * @return
     * @throws Exception
     */
    public static OtherUser getMobileUserMessage(String code) throws Exception {
        WxResult authorization = getMobileUserAccessToken(code);

        Status status = authorization.getStatus();
        if (status.getCode() == TransErrorCode.SUCCESS.getCode()) {
            return getUserMessage(authorization);
        }
        return new OtherUser(authorization.getStatus());
    }

    /**
     * 获取移动端微信用户的信息
     * 
     * @param wxResult 通过WxUtils.getUserAccessToken(String code)查出来的WxResult
     * @return
     * @throws Exception
     */
    public static OtherUser getUserMessage(WxResult wxResult) throws Exception {
        String urlResult = doGet(String.format(USER_INFO_URL, wxResult.getAccessToken(), wxResult.getOpenid()));
        if (isBlankObject(urlResult)) {
            return new OtherUser(TransErrorCode.OTHER_ERROR.getCode(), "通过移动端微信用户认证AccessToken获取用户信息,路径没有返回任何值!");
        }

        JSONObject jsonObject = jsonToObject(urlResult, JSONObject.class);
        Integer errorCode = jsonObject.getInteger(FIELD_ERRCODE);
        if (isNotBlankObject(errorCode)) {
            return new OtherUser(errorCode, jsonObject.getString(FIELD_ERRMSG));
        }

        OtherUser user = new OtherUser(TransErrorCode.SUCCESS.getCode());
        String unionid = jsonObject.getString(FIELD_UNIONID);
        if (isNotBlankObject(unionid)) {
            user.setAppId(unionid);
        } else {
            user.setAppId(jsonObject.getString(FIELD_FIELD_OPENID));
        }
        user.setNickname(jsonObject.getString(FIELD_NICKNAME));
        user.setSex(Integer.valueOf(jsonObject.getString(FIELD_SEX)));
        user.setHeadImg(jsonObject.getString(FIELD_HEADIMGURL));
        return user;
    }

    /**
     * 微信校验签名(怀疑是测试能不能连上自己的项目的)
     * 
     * @param signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数,nonce参数
     * @param timestamp 时间戳
     * @param nonce     随机数
     * @param signType  签名类型
     * @return
     * @throws Exception
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce, String signType)
        throws Exception {
        String[] arr = new String[] {TOKEN, timestamp, nonce};

        // 将token、timestamp、nonce三个参数进行字典序排序
        Arrays.sort(arr);

        // 将三个参数拼接在一起
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            content.append(arr[i]);
        }

        // 进行SHA1加密
        String encryption = null;
        if (EEncryptionType.SHA_1.getType().equals(signType)) {
            encryption = Sha1Util.encryption(content.toString());
        } else {
            throw new TransException("无此加密类型！");
        }

        // 对比签名
        return isNotBlankObject(encryption) ? encryption.equals(signature) : false;
    }

    /**
     * 微信校验签名(怀疑是测试能不能连上自己的项目的)
     * 
     * @param signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数,nonce参数
     * @param timestamp 时间戳
     * @param nonce     随机数
     * @param signType  签名类型
     * @return
     * @throws Exception
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce, EEncryptionType signType)
        throws Exception {
        return checkSignature(signature, timestamp, nonce, signType.getType());
    }

    /**
     * 获取微信调用JS接口的临时票据
     * 
     * @return
     * @throws Exception
     */
    public static String getJsapiTicket() throws Exception {
        WxResult accessToken = getAccessToken();
        if (isBlankObject(!accessToken.getStatus().isSuccess())) {
            return null;
        }
        return getJsapiTicket(accessToken.getAccessToken());
    }

    /**
     * 获取微信调用JS接口的临时票据
     * 
     * @param accessToken
     * @return
     * @throws Exception
     */
    public static String getJsapiTicket(String accessToken) throws Exception {
        String string = get(JSAPI_TICKET_CACHE_PREFIX, String.class);
        if (isNotBlankObject(string)) {
            return string;
        }

        String doGet = doGet(String.format(JSAPI_TICKET_URL, accessToken));
        if (isBlankObject(doGet)) {
            return null;
        }
        JSONObject jsonToObject = jsonToObject(doGet, JSONObject.class);
        String object = jsonToObject.getString(FIELD_TICKET);
        if (isBlankObject(object)) {
            return null;
        }

        setexYes(JSAPI_TICKET_CACHE_PREFIX, object, JSAPI_TICKET_CACHE_TIMEOUT);
        return object;
    }

    /**
     * 获取微信签名WxResult对象
     * 
     * @param wxResult
     * @return
     * @throws Exception
     */
    public static WxResult getSignatureBean(String url) throws Exception {
        WxResult wxResult = new WxResult();
        wxResult.setUrl(url);
        wxResult.setNonceStr(getNonceStr());
        wxResult.setAppid(getDefaultAppId());
        wxResult.setTimestamp(System.currentTimeMillis() / 1000);
        return wxResult;
    }

    /**
     * 获取微信签名
     * 
     * @param wxResult 微信签名WxResult对象,通过WxUtils.getSignatureBean获取
     * @return
     * @throws Exception 
     */
    public static WxResult getSignature(String url) throws Exception {
        WxResult wxResult = getSignatureBean(url);
        String signature = getSignature(wxResult, EEncryptionType.SHA_1);
        if (isBlankObject(signature)) {
            return null;
        }

        wxResult.setSign(signature);
        return wxResult;
    }

    /**
     * 获取微信签名
     * 
     * @param wxResult  微信签名WxResult对象,通过WxUtils.getSignatureBean获取
     * @param signType  签名类型
     * @return
     * @throws Exception 
     */
    public static String getSignature(WxResult wxResult, String signType) throws Exception {
        String jsapiTicket = getJsapiTicket();
        if (isBlankObject(jsapiTicket)) {
            return null;
        }

        if (EEncryptionType.SHA_1.getType().equals(signType)) {
            return Sha1Util.encryption(String.format(SIGNATURE_PARAM, jsapiTicket, wxResult.getNonceStr(),
                wxResult.getTimestamp(), wxResult.getUrl()));
        } else {
            throw new TransException("无此加密类型！");
        }
    }

    /**
     * 获取微信签名
     * 
     * @param wxResult  微信签名WxResult对象,通过WxUtils.getSignatureBean获取
     * @param signType  签名类型
     * @return
     * @throws Exception 
     */
    public static String getSignature(WxResult wxResult, EEncryptionType signType) throws Exception {
        return getSignature(wxResult, signType.getType());
    }

    /**
     * 获取微信公众号二维码(永久的二维码)
     * 
     * @param content 给公众号发送的信息
     * @return
     * @throws Exception 
     */
    public static String getQrcode(int content) throws Exception {
        HashMap<String, Object> sceneContent = new HashMap<>(2);
        sceneContent.put(FIELD_SCENE_ID, content);

        HashMap<String, Object> scene = new HashMap<>(2);
        scene.put(FIELD_SCENE, sceneContent);

        HashMap<String, Object> hashMap = new HashMap<>(5);
        hashMap.put(FIELD_ACTION_NAME, FIELD_ACTION_NAME_VALUE_LIMIT_SCENE);
        hashMap.put(FIELD_ACTION_INFO, scene);

        return getQrcode(hashMap);
    }

    /**
     * 获取微信公众号二维码(临时的二维码)
     * 
     * @param content 给公众号发送的信息
     * @param timeout 二维码的有效时间(单位: 秒)
     * @return
     * @throws Exception 
     */
    public static String getQrcode(int content, int timeout) throws Exception {
        HashMap<String, Object> sceneContent = new HashMap<>(2);
        sceneContent.put(FIELD_SCENE_ID, content);

        HashMap<String, Object> scene = new HashMap<>(2);
        scene.put(FIELD_SCENE, sceneContent);

        HashMap<String, Object> hashMap = new HashMap<>(5);
        hashMap.put(FIELD_ACTION_NAME, FIELD_ACTION_NAME_VALUE_SCENE);
        hashMap.put(FIELD_ACTION_INFO, scene);

        return getQrcode(hashMap);
    }

    /**
     * 获取微信公众号二维码(永久的二维码)
     * 
     * @param content 给公众号发送的信息
     * @return
     * @throws Exception 
     */
    public static String getQrcode(String content) throws Exception {
        HashMap<String, Object> sceneContent = new HashMap<>(2);
        sceneContent.put(FIELD_SCENE_STR, content);

        HashMap<String, Object> scene = new HashMap<>(2);
        scene.put(FIELD_SCENE, sceneContent);

        HashMap<String, Object> hashMap = new HashMap<>(5);
        hashMap.put(FIELD_ACTION_NAME, FIELD_ACTION_NAME_VALUE_LIMIT_STR_SCENE);
        hashMap.put(FIELD_ACTION_INFO, scene);

        return getQrcode(hashMap);
    }

    /**
     * 获取微信公众号二维码(临时的二维码)
     * 
     * @param content 给公众号发送的信息
     * @param timeout 二维码的有效时间(单位: 秒)
     * @return
     * @throws Exception 
     */
    public static String getQrcode(String content, int timeout) throws Exception {
        HashMap<String, Object> sceneContent = new HashMap<>(2);
        sceneContent.put(FIELD_SCENE_STR, content);

        HashMap<String, Object> scene = new HashMap<>(2);
        scene.put(FIELD_SCENE, sceneContent);

        HashMap<String, Object> hashMap = new HashMap<>(5);
        hashMap.put(FIELD_ACTION_NAME, FIELD_ACTION_NAME_VALUE_STR_SCENE);
        hashMap.put(FIELD_ACTION_INFO, scene);

        return getQrcode(hashMap);
    }

    /**
     * 获取微信公众号二维码
     * 
     * @param map 获取微信二维码的参数
     * @return
     * @throws Exception 
     */
    private static String getQrcode(Map<String, Object> map) throws Exception {
        WxResult accessToken = getAccessToken();
        if (isBlankObject(!accessToken.getStatus().isSuccess())) {
            return null;
        }

        return getQrcode(accessToken.getAccessToken(), map);
    }

    /**
     * 获取微信公众号二维码(永久的二维码)
     * 
     * @param accessToken 用微信开放接口的票据
     * @param content 给公众号发送的信息
     * @return
     * @throws Exception 
     */
    public static String getQrcodeByAccessToken(String accessToken, int content) throws Exception {
        HashMap<String, Object> sceneContent = new HashMap<>(2);
        sceneContent.put(FIELD_SCENE_ID, content);

        HashMap<String, Object> scene = new HashMap<>(2);
        scene.put(FIELD_SCENE, sceneContent);

        HashMap<String, Object> hashMap = new HashMap<>(5);
        hashMap.put(FIELD_ACTION_NAME, FIELD_ACTION_NAME_VALUE_LIMIT_SCENE);
        hashMap.put(FIELD_ACTION_INFO, scene);

        return getQrcode(accessToken, hashMap);
    }

    /**
     * 获取微信公众号二维码(临时的二维码)
     * 
     * @param accessToken 用微信开放接口的票据
     * @param content 给公众号发送的信息
     * @param timeout 二维码的有效时间(单位: 秒)
     * @return
     * @throws Exception 
     */
    public static String getQrcode(String accessToken, int content, int timeout) throws Exception {
        HashMap<String, Object> sceneContent = new HashMap<>(2);
        sceneContent.put(FIELD_SCENE_ID, content);

        HashMap<String, Object> scene = new HashMap<>(2);
        scene.put(FIELD_SCENE, sceneContent);

        HashMap<String, Object> hashMap = new HashMap<>(5);
        hashMap.put(FIELD_ACTION_NAME, FIELD_ACTION_NAME_VALUE_SCENE);
        hashMap.put(FIELD_ACTION_INFO, scene);

        return getQrcode(accessToken, hashMap);
    }

    /**
     * 获取微信公众号二维码(永久的二维码)
     * 
     * @param accessToken 用微信开放接口的票据
     * @param content 给公众号发送的信息
     * @return
     * @throws Exception 
     */
    public static String getQrcode(String accessToken, String content) throws Exception {
        HashMap<String, Object> sceneContent = new HashMap<>(2);
        sceneContent.put(FIELD_SCENE_STR, content);

        HashMap<String, Object> scene = new HashMap<>(2);
        scene.put(FIELD_SCENE, sceneContent);

        HashMap<String, Object> hashMap = new HashMap<>(5);
        hashMap.put(FIELD_ACTION_NAME, FIELD_ACTION_NAME_VALUE_LIMIT_STR_SCENE);
        hashMap.put(FIELD_ACTION_INFO, scene);

        return getQrcode(accessToken, hashMap);
    }

    /**
     * 获取微信公众号二维码(临时的二维码)
     * 
     * @param accessToken 用微信开放接口的票据
     * @param content 给公众号发送的信息
     * @param timeout 二维码的有效时间(单位: 秒)
     * @return
     * @throws Exception 
     */
    public static String getQrcode(String accessToken, String content, int timeout) throws Exception {
        HashMap<String, Object> sceneContent = new HashMap<>(2);
        sceneContent.put(FIELD_SCENE_STR, content);

        HashMap<String, Object> scene = new HashMap<>(2);
        scene.put(FIELD_SCENE, sceneContent);

        HashMap<String, Object> hashMap = new HashMap<>(5);
        hashMap.put(FIELD_ACTION_NAME, FIELD_ACTION_NAME_VALUE_STR_SCENE);
        hashMap.put(FIELD_EXPIRE_SECONDS, timeout);
        hashMap.put(FIELD_ACTION_INFO, scene);

        return getQrcode(accessToken, hashMap);
    }

    /**
     * 获取微信公众号二维码
     * 
     * @param accessToken 用微信开放接口的票据
     * @param map 获取微信二维码的参数
     * @return
     * @throws Exception
     */
    public static String getQrcode(String accessToken, Map<String, Object> map) throws Exception {
        return doPostByParam(String.format(QRCODE_TICKET, accessToken), objectToJson(map));
    }

    /**
     * 获取微信支付订单二维码CodeUrl
     * 
     * @param treeMap 获取微信二维码的参数
     * @return
     * @throws Exception
     */
    public static String getBizPayUrlInitUser(TreeMap<String, Object> treeMap) throws Exception {
        // 添加用户参数
        initBizPayUrlUser(treeMap);

        // 获取CodeUrl
        return getBizPayUrl(treeMap);
    }

    /**
     * 获取微信支付订单二维码CodeUrl
     * 
     * @param treeMap 获取微信二维码的参数
     * @return
     * @throws Exception
     */
    public static String getBizPayUrl(TreeMap<String, Object> treeMap) throws Exception {
        // 添加必须的参数
        initBizPayUrl(treeMap);

        // 拼接CodeUrl
        return String.format(BIZPAYURL, treeMap.get(FIELD_APPID), treeMap.get(FIELD_MCH_ID),
            treeMap.get(FIELD_NONCE_STR), treeMap.get(FIELD_PRODUCT_ID), treeMap.get(FIELD_TIME_STAMP),
            getPaySignObject(treeMap, getApiKey(), EEncryptionType.MD5));
    }

    /**
     * 给获取微信支付订单二维码CodeUrl地址添加用户参数
     * 
     * @param map
     * @throws Exception 
     */
    public static void initBizPayUrlUser(TreeMap<String, Object> map) throws Exception {
        map.put(FIELD_APPID, getDefaultAppId());
        map.put(FIELD_MCH_ID, getMchid());
    }

    /**
     * 给获取微信支付订单二维码CodeUrl地址添加固定参数
     * 
     * @param map
     * @throws Exception 
     */
    public static void initBizPayUrl(TreeMap<String, Object> map) throws Exception {
        map.put(FIELD_TIME_STAMP, System.currentTimeMillis() / 1000);
        map.put(FIELD_NONCE_STR, getNonceStr());
    }

    /**
     * 微信的统一支付页面(根据不同的参数调起不同的支付页面(方式))
     * 
     * @param map
     * @return
     * @throws Exception
     */
    public static WxResult unifiedorder(TreeMap<String, Object> map) throws Exception {
        // // 要返回的数据
        // WxPayBean wxPayBean = new WxPayBean();
        //
        // // 添加签名
        // map.put(FIELD_SIGN, getPaySignObject(map));
        // System.out.println(map);
        // // 调用微信统一支付
        // String doPostByParam = HttpClientUtils.doPostByParam(UNIFIEDORDER, XmlUtils.mapToXml(map));
        // // 得到微信统一支付结果
        // TreeMap<String, String> xmlToMap = new TreeMap<>(XmlUtils.xmlToMap(doPostByParam));
        //
        // // 设置返回的状态码
        // String returnCode = xmlToMap.get(FIELD_RETURN_CODE);
        // String resultCode = xmlToMap.get(FIELD_RESULT_CODE);
        // wxPayBean.setReturnCode(returnCode);
        // wxPayBean.setResultCode(resultCode);
        // // 判断第一个状态码是否成功
        // if (!WxConstant.SUCCESS.equals(returnCode)) {
        // wxPayBean.setReturnMsg(xmlToMap.get(FIELD_RETURN_MSG));
        // return wxPayBean;
        // }
        //
        // // 设置得到的数据
        // String sign = xmlToMap.get(FIELD_SIGN);
        // wxPayBean.setAppid(xmlToMap.get(FIELD_APPID));
        // wxPayBean.setMchId(xmlToMap.get(MCHID));
        // wxPayBean.setDeviceInfo(xmlToMap.get(FIELD_DEVICE_INFO));
        // wxPayBean.setNonceStr(xmlToMap.get(FIELD_NONCE_STR));
        // wxPayBean.setSign(sign);
        //
        // // 判断第二个状态码是否成功
        // if (!WxConstant.SUCCESS.equals(resultCode)) {
        // wxPayBean.setResultCode(xmlToMap.get(FIELD_RESULT_CODE));
        // wxPayBean.setErrCodeDes(xmlToMap.get(FIELD_ERR_CODE_DES));
        // return wxPayBean;
        // }
        //
        // // 校验签名
        // if (StringUtils.isBlankObject(sign) || !sign.equals(getPaySignString(xmlToMap))) {
        // wxPayBean.setReturnCode(WxConstant.FAIL);
        // wxPayBean.setReturnMsg("统一支付接口返回的签名错误!");
        // wxPayBean.setResultCode(WxConstant.FAIL);
        // return wxPayBean;
        // }
        //
        // wxPayBean.setAppid(xmlToMap.get(FIELD_TRADE_TYPE));
        // wxPayBean.setPrepayId(xmlToMap.get(FIELD_PREPAY_ID));
        // wxPayBean.setCodeUrl(xmlToMap.get(FIELD_CODE_URL));
        // return wxPayBean;
        return null;
    }

    /**
     * 给微信统一支付添加用户参数
     * 
     * @param map
     * @throws Exception 
     */
    public static void initUnifiedorderUser(TreeMap<String, Object> map) throws Exception {
        map.put(FIELD_APPID, getDefaultAppId());
        map.put(FIELD_MCH_ID, getMchid());
    }

    /**
     * 给微信统一支付添加固定参数
     * 
     * @param map
     * @param payOrder 前端传过来的参数
     * @throws Exception 
     */
    public static void initUnifiedorder(TreeMap<String, Object> map, PayOrder payOrder) throws Exception {
        map.put(WxUtil.FIELD_DEVICE_INFO, payOrder.getDeviceInfo());
        map.put(WxUtil.FIELD_NONCE_STR, WxUtil.getNonceStr());
        map.put(WxUtil.FIELD_OUT_TRADE_NO, generateGuid());
        map.put(WxUtil.FIELD_SPBILL_CREATE_IP, IpConfig.addressPublic);
        map.put(FIELD_NOTIFY_URL, PAY_CALL_BACK);
    }

    /**
     * 验证微信支付签名是否正确
     * 
     * @param map       从请求中获取的参数
     * @param mchKey    商户密钥(API密钥)
     * @param signType  签名类型
     * @throws Exception 
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static boolean paySignIsSuccessString(TreeMap<String, String> map, String mchKey, String signType)
        throws Exception {
        return paySignIsSuccessObject((TreeMap)map, mchKey, signType);
    }

    /**
     * 验证微信支付签名是否正确
     * 
     * @param map       从请求中获取的参数
     * @param mchKey    商户密钥(API密钥)
     * @param signType  签名类型
     * @throws Exception 
     */
    public static boolean paySignIsSuccessString(TreeMap<String, String> map, String mchKey, EEncryptionType signType)
        throws Exception {
        return paySignIsSuccessString(map, mchKey, signType.getType());
    }

    /**
     * 验证微信支付签名是否正确
     * 
     * @param map       从请求中获取的参数
     * @param mchKey    商户密钥(API密钥)
     * @param signType  签名类型
     * @throws Exception 
     */
    public static boolean paySignIsSuccessObject(TreeMap<String, Object> map, String mchKey, String signType)
        throws Exception {
        // 获取签名
        String sign = (String)map.get(WxUtil.FIELD_SIGN);

        // 验证签名
        return getPaySignObject(map, mchKey, signType).equals(sign);
    }

    /**
     * 验证微信支付签名是否正确
     * 
     * @param map       从请求中获取的参数
     * @param mchKey    商户密钥(API密钥)
     * @param signType  签名类型
     * @throws Exception 
     */
    public static boolean paySignIsSuccessObject(TreeMap<String, Object> map, String mchKey, EEncryptionType signType)
        throws Exception {
        return paySignIsSuccessObject(map, mchKey, signType.getType());
    }

    /**
     * 获取微信支付签名参数
     * 1.将集合中的值拼接成一个Get方式提交的参数字符串
     * 2.按Key的字母顺序排序
     * 3.值为空的不参与
     * 4.Key为sign的不参与
     * 5.拼接完Map中的值最后再拼一个key={商户密钥}(商户密钥为对应商户的密钥)
     * 6.将字符串加密,微信默认为MD5加密,可以设置为其他加密方式
     * 
     * @param map       从请求中获取的参数
     * @param mchKey    商户密钥(API密钥)
     * @param signType  签名类型
     * @throws Exception 
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static String getPaySignString(TreeMap<String, String> map, String mchKey, String signType)
        throws Exception {
        return getPaySignObject((TreeMap)map, mchKey, signType);
    }

    /**
     * 获取微信支付签名参数
     * 1.将集合中的值拼接成一个Get方式提交的参数字符串
     * 2.按Key的字母顺序排序
     * 3.值为空的不参与
     * 4.Key为sign的不参与
     * 5.拼接完Map中的值最后再拼一个key={商户密钥}(商户密钥为对应商户的密钥)
     * 6.将字符串加密,微信默认为MD5加密,可以设置为其他加密方式
     * 
     * @param map       从请求中获取的参数
     * @param mchKey    商户密钥(API密钥)
     * @param signType  签名类型
     * @throws Exception 
     */
    public static String getPaySignString(TreeMap<String, String> map, String mchKey, EEncryptionType signType)
        throws Exception {
        return getPaySignString(map, mchKey, signType.getType());
    }

    /**
     * 获取微信支付签名参数
     * 1.将集合中的值拼接成一个Get方式提交的参数字符串
     * 2.按Key的字母顺序排序
     * 3.值为空的不参与
     * 4.Key为sign的不参与
     * 5.拼接完Map中的值最后再拼一个key={商户密钥}(商户密钥为对应商户的密钥)
     * 6.将字符串加密,微信默认为MD5加密,可以设置为其他加密方式
     * 
     * @param map 从请求中获取的参数
     * @param mchKey 商户密钥(API密钥)
     * @param signType  签名类型
     * @throws Exception 
     */
    public static String getPaySignObject(TreeMap<String, Object> map, String mchKey, String signType)
        throws Exception {
        StringBuilder sign = new StringBuilder();
        for (Entry<String, Object> entry : map.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();

            if (isBlank(value) || FIELD_SIGN.equals(key)) {
                continue;
            }

            getPaySign(sign, key, value);
        }

        return getPaySign(sign, signType, mchKey);
    }

    /**
     * 获取微信支付签名参数
     * 1.将集合中的值拼接成一个Get方式提交的参数字符串
     * 2.按Key的字母顺序排序
     * 3.值为空的不参与
     * 4.Key为sign的不参与
     * 5.拼接完Map中的值最后再拼一个key={商户密钥}(商户密钥为对应商户的密钥)
     * 6.将字符串加密,微信默认为MD5加密,可以设置为其他加密方式
     * 
     * @param map 从请求中获取的参数
     * @param mchKey 商户密钥(API密钥)
     * @param signType  签名类型
     * @throws Exception 
     */
    public static String getPaySignObject(TreeMap<String, Object> map, String mchKey, EEncryptionType signType)
        throws Exception {
        return getPaySignObject(map, mchKey, signType.getType());
    }

    /**
     * 获取微信支付签名参数
     * 
     * @param sign 签名
     * @param key 集合中的Key
     * @param value 集合中的Value
     */
    private static void getPaySign(StringBuilder sign, String key, Object value) {
        sign.append(key);
        sign.append("=");
        sign.append(value);
        sign.append("&");
    }

    /**
     * 获取微信支付签名参数
     * 
     * @param sign      签名
     * @param signType  签名类型
     * @param key       商户密钥(API密钥)
     * @return 
     * @throws Exception 
     */
    private static String getPaySign(StringBuilder sign, String signType, String key) throws Exception {
        sign.append(FIELD_MCH_KEY_PARAM);
        sign.append(key);
        String string = sign.toString();
        if (EEncryptionType.MD5.getType().equals(signType)) {
            return Md5Util.encryption(string).toUpperCase();
        } else if (EEncryptionType.HMACS_HA256.getType().equals(signType)) {
            return Hmacsha256Util.encryption(string, key).toUpperCase();
        } else {
            throw new TransException("无此加密类型！");
        }
    }

    /**
     * 获取微信支付生成订单成功后要返回的信息
     * 
     * @return 
     */
    public static String getPayOrderSuccessMessage(TreeMap<String, Object> xmlToMap) {
        // 版本一
        // TreeMap<String, String> resMap = new TreeMap<>();
        // resMap.put(WxUtils.FIELD_RETURN_CODE, "<![CDATA[SUCCESS]]>");
        // resMap.put(WxUtils.FIELD_APPID, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_APPID)));
        // resMap.put(WxUtils.FIELD_MCH_ID, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_MCH_ID)));
        // resMap.put(WxUtils.FIELD_NONCE_STR, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_NONCE_STR)));
        // resMap.put(WxUtils.FIELD_PREPAY_ID, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_PREPAY_ID)));
        // resMap.put(WxUtils.FIELD_RESULT_CODE, "<![CDATA[SUCCESS]]>");
        // resMap.put(WxUtils.FIELD_SIGN, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_SIGN)));
        // return getRequestXml(resMap);

        System.out.println(xmlToMap);
        // 版本二
        return String.format(WxConstant.RES_SUCCESS_CREATE_ORDER, xmlToMap.get(FIELD_APPID), xmlToMap.get(FIELD_MCH_ID),
            xmlToMap.get(FIELD_NONCE_STR), xmlToMap.get(FIELD_PREPAY_ID), xmlToMap.get(FIELD_SIGN));
    }

    /**
     * 获取微信支付生成订单失败后要返回的信息
     * 
     * @return 
     */
    public static String getPayOrderFailReturnCodeMessage(TreeMap<String, Object> xmlToMap) {
        // 版本一
        // TreeMap<String, String> resMap = new TreeMap<>();
        // resMap.put(WxUtils.FIELD_RETURN_CODE, "<![CDATA[FAIL]]>");
        // resMap.put(WxUtils.FIELD_RETURN_CODE, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_RETURN_MSG)));
        // resMap.put(WxUtils.FIELD_APPID, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_APPID)));
        // resMap.put(WxUtils.FIELD_MCH_ID, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_MCH_ID)));
        // resMap.put(WxUtils.FIELD_NONCE_STR, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_NONCE_STR)));
        // resMap.put(WxUtils.FIELD_PREPAY_ID, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_PREPAY_ID)));
        // resMap.put(WxUtils.FIELD_RESULT_CODE, "<![CDATA[FAIL]]>");
        // resMap.put(WxUtils.FIELD_SIGN, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_SIGN)));
        // return getRequestXml(resMap);

        // 版本二
        return String.format(WxConstant.RES_FAIL_CREATE_ORDER_RETURN_CODE, xmlToMap.get(FIELD_RETURN_MSG),
            xmlToMap.get(FIELD_APPID), xmlToMap.get(FIELD_MCH_ID), xmlToMap.get(FIELD_NONCE_STR),
            xmlToMap.get(FIELD_PREPAY_ID), xmlToMap.get(FIELD_SIGN));
    }

    /**
     * 获取微信支付生成订单失败后要返回的信息
     * 
     * @return 
     */
    public static String getPayOrderFailResultCodeMessage(TreeMap<String, Object> xmlToMap) {
        // 版本一
        // TreeMap<String, String> resMap = new TreeMap<>();
        // resMap.put(WxUtils.FIELD_RETURN_CODE, "<![CDATA[SUCCESS]]>");
        // resMap.put(WxUtils.FIELD_APPID, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_APPID)));
        // resMap.put(WxUtils.FIELD_MCH_ID, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_MCH_ID)));
        // resMap.put(WxUtils.FIELD_NONCE_STR, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_NONCE_STR)));
        // resMap.put(WxUtils.FIELD_PREPAY_ID, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_PREPAY_ID)));
        // resMap.put(WxUtils.FIELD_RESULT_CODE, "<![CDATA[FAIL]]>");
        // resMap.put(WxUtils.FIELD_RETURN_CODE, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_ERR_CODE_DES)));
        // resMap.put(WxUtils.FIELD_SIGN, String.format("<![CDATA[%s]]>", xmlToMap.get(FIELD_SIGN)));
        // return getRequestXml(resMap);

        // 版本二
        return String.format(WxConstant.RES_FAIL_CREATE_ORDER_RETURN_CODE, xmlToMap.get(FIELD_APPID),
            xmlToMap.get(FIELD_MCH_ID), xmlToMap.get(FIELD_NONCE_STR), xmlToMap.get(FIELD_PREPAY_ID),
            xmlToMap.get(FIELD_ERR_CODE_DES), xmlToMap.get(FIELD_SIGN));
    }

    /**
     * 获取微信支付成功后要返回的信息
     * 
     * @return 
     */
    public static String getPaySuccessMessage() {
        // 版本一
        // TreeMap<String, String> resMap = new TreeMap<>();
        // resMap.put(WxUtils.FIELD_RETURN_CODE, "<![CDATA[FAIL]]>");
        // resMap.put(WxUtils.FIELD_RETURN_MSG, "<![CDATA[FAIL]]>");
        // return getRequestXml(resMap);

        // 版本二
        return WxConstant.RES_SUCCESS_UNIFIEDORDER;
    }

    /**
     * 获取微信支付失败后要返回的信息
     * 
     * @return 
     */
    public static String getPayFailMessage() {
        // 版本一
        // TreeMap<String, String> resMap = new TreeMap<>();
        // resMap.put(WxUtils.FIELD_RETURN_CODE, "<![CDATA[FAIL]]>");
        // resMap.put(WxUtils.FIELD_RETURN_MSG, "<![CDATA[FAIL]]>");
        // return getRequestXml(resMap);

        // 版本二
        return WxConstant.RES_FAIL_UNIFIEDORDER;
    }

    /**
     * 返回微信支付成功后的异步通知结果
     * 
     * @param resMap
     * @return
     */
    public static String getRequestXml(TreeMap<String, String> resMap) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");

        Set<Entry<String, String>> entrySet = resMap.entrySet();
        Iterator<Entry<String, String>> iterator = entrySet.iterator();
        while (iterator.hasNext()) {
            Entry<String, String> next = iterator.next();
            String k = next.getKey();
            sb.append("<" + k + ">" + next.getValue() + "</" + k + ">");
        }

        sb.append("</xml>");
        return sb.toString();
    }

    /**
     * 获取生成密钥的随机字符串 
     * 
     * @return
     */
    public static String getNonceStr() {
        // TODO
        return NONCE_STR_VALUE;
    }

    /**
     * 设置微信证书私钥
     * 
     * @return
     */
    public static void setPrivateKey() {
        try {
            PRIVATE_KEY = PayKit.getPrivateKey(WxConstant.CA_CERT_FILE_PATH_PEM_KEY);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取微信证书私钥
     * 
     * @return
     */
    private static PrivateKey getPrivateKey() {
        try {
            return PayKit.getPrivateKey(WxConstant.CA_CERT_FILE_PATH_PEM_KEY);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 小程序登录(通过jsCode获取unionid,openid,session_key)
     * 
     * @return
     * @throws Exception 
     */
    public static WxResult xcxLogin(String jsCode) throws Exception {
        String appid = getDefaultAppId();
        String appsecret = getDefaultAppsecret();

        String doGet = doGet(
            String.format(EWxApiType.XCX_GET_OPENID_AND_UNIONID_AND_SESSION_KEY.getType(), appid, appsecret, jsCode));

        WxResponse jsonToObject = jsonToObject(doGet);
        Status status = jsonToObject.getCusStatus();
        if (status.getCode() != TransErrorCode.SUCCESS.getCode()) {
            throw new TransException("小程序登录凭证校验失败: " + status.getMsg());
        }

        WxResult wxResult = new WxResult(status);
        wxResult.setOpenid(jsonToObject.getOpenid());
        wxResult.setUnionid(jsonToObject.getUnionid());
        wxResult.setSessionKey(jsonToObject.getSessionKey());
        return wxResult;
    }

    /**
     * 获取小程序码
     * 
     * @return
     * @throws Exception
     */
    public static byte[] getWxAcodeunLimit() throws Exception {
        return getWxAcodeunLimit(TokenUtil.getUser().getInvitationCode());
    }

    /**
     * 获取小程序码
     * 
     * @param scene  R   参数
     * @return
     * @throws Exception
     */
    public static byte[] getWxAcodeunLimit(String scene) throws Exception {
        WxParam wxPar = new WxParam();
        wxPar.setAppid(getDefaultAppId());
        wxPar.setSecret(getDefaultAppsecret());
        // 获取小程序码
        return getWxAcodeunLimit(scene, getAccessToken(wxPar).getAccessToken());
    }

    /**
     * 获取小程序码
     * 
     * @param scene         R   参数
     * @param access_token  R   接口调用凭证
     * @return
     * @throws Exception
     */
    public static byte[] getWxAcodeunLimit(String scene, String accessToken) throws Exception {
        WxParam wxParam = new WxParam();
        wxParam.setScene(scene);

        // 获取小程序码
        return getWxAcodeunLimit(wxParam, accessToken);
    }

    /**
     * 获取小程序码
     * 
     * @param access_token  R   接口调用凭证
     * @param scene         R   参数
     * @param line_color    C   auto_color为 false时生效,使用rgb设置颜色
     * @param page          O   要跳转的页面
     * @param width         O   二维码的宽度
     * @param auto_color    O   自动配置线条颜色
     * @param is_hyaline    O   是否需要透明底色
     * @return
     * @throws Exception
     */
    public static byte[] getWxAcodeunLimit(WxParam wxParam) throws Exception {
        // 获取access_token
        WxParam wxPar = new WxParam();
        wxPar.setAppid(getDefaultAppId());
        wxPar.setSecret(getDefaultAppsecret());

        // 获取小程序码
        return getWxAcodeunLimit(wxParam, getAccessToken(wxPar).getAccessToken());
    }

    /**
     * 获取小程序码
     * 
     * @param access_token  R   接口调用凭证
     * @param scene         R   参数
     * @param line_color    C   auto_color为 false时生效,使用rgb设置颜色
     * @param page          O   要跳转的页面
     * @param width         O   二维码的宽度
     * @param auto_color    O   自动配置线条颜色
     * @param is_hyaline    O   是否需要透明底色
     * @return
     * @throws Exception
     */
    public static byte[] getWxAcodeunLimit(WxParam wxParam, String accessToken) throws Exception {
        // 获取小程序码
        byte[] res =
            doPostByteByParam(String.format(EWxApiType.XCX_GET_MINI_PROGRAM_CODE.getType(), accessToken), wxParam);

        ByteArrayInputStream byteArrayInputStream = null;
        try {
            byteArrayInputStream = new ByteArrayInputStream(res);
            BufferedImage bi = ImageIO.read(byteArrayInputStream);
            if (bi == null) {
                WxResponse jsonToObject = jsonToObject(new String(res));
                Status status = jsonToObject.getCusStatus();
                throw new TransException("获取小程序码失败: " + status.getMsg());
            }
        } finally {
            closeStream(byteArrayInputStream);
        }

        return res;
    }

    /**
     * 获取获取小程序码的默认参数
     * 
     * @return
     * @throws Exception 
     */
    @SuppressWarnings("unused")
    private static WxParam getWxAcodeunLimitParam() throws Exception {
        return getWxAcodeunLimitParam("");
    }

    /**
     * 获取获取小程序码的默认参数
     * 
     * @param scene R   参数
     * @return
     * @throws Exception 
     */
    private static WxParam getWxAcodeunLimitParam(String scene) throws Exception {
        WxParam wxPar = new WxParam();
        wxPar.setAppid(getDefaultAppId());
        wxPar.setSecret(getDefaultAppsecret());

        WxParam wxParam = new WxParam();
        wxParam.setScene(scene);
        wxParam.setAccessToken(getAccessToken(wxPar).getAccessToken());
        return wxParam;
    }

    /**
     * 发送订阅消息
     * 
     * @param openid        Openid
     * @param templateId    模板ID
     * @param datas         内容
     * @return
     * @throws Exception
     */
    public static WxResult sendSubscribeMessage(String openid, String templateId, Map<String, String> datas)
        throws Exception {
        // 获取AccessToken
        WxResult accessToken = getAccessToken();

        // 组装内容
        Map<String, WxPamValue> d = new HashMap<>();
        Set<Entry<String, String>> entrySet = datas.entrySet();
        for (Entry<String, String> entry : entrySet) {
            WxPamValue value = new WxPamValue();
            value.setValue(entry.getValue());
            d.put(entry.getKey(), value);
        }
        // 组装参数
        WxParam param = new WxParam();
        param.setTouser(openid);
        param.setTemplateId(templateId);
        param.setData(d);
        // 发送消息
        String doGet =
            doPostByParam(String.format(EWxApiType.XCX_SEND_SUBSCRIBE_MESSAGE.getType(), accessToken.getAccessToken()),
                objectToJson(param));

        WxResponse jsonToObject = jsonToObject(doGet);
        Status cusStatus = jsonToObject.getCusStatus();
        if (cusStatus.getCode() != TransErrorCode.SUCCESS.getCode()) {
            throw new TransException("发送微信订阅消息失败: " + cusStatus.getMsg());
        }

        return new WxResult(cusStatus);
    }

    /**
     * 获取商户号
     * 
     * @return
     * @throws IOException
     */
    public static String getMchid() throws IOException {
        return getByMapStr(WxConstant.MCHID_CACHE_NAME);
    }

    /**
     * 获取微信商户密钥(API密钥)
     * 
     * @return
     * @throws IOException
     */
    public static String getApiKey() throws IOException {
        return getByMapStr(WxConstant.APIKEY_CACHE_NAME);
    }

    /**
     * 获取默认Appid
     * 
     * @return
     * @throws IOException
     */
    public static String getDefaultAppId() throws IOException {
        return getByMapStr(WxConstant.DEFAULT_APPID_CACHE_NAME);
    }

    /**
     * 获取默认Appsecret
     * 
     * @return
     * @throws IOException
     */
    public static String getDefaultAppsecret() throws IOException {
        return getByMapStr(WxConstant.DEFAULT_APPSECRET_CACHE_NAME);
    }

    /**
     * 获取公众平台Appid
     * 
     * @return
     * @throws IOException
     */
    public static String getGzAppId() throws IOException {
        return getByMapStr(WxConstant.GZ_APPID_CACHE_NAME);
    }

    /**
     * 获取公众平台Appsecret
     * 
     * @return
     * @throws IOException
     */
    public static String getGzAppsecret() throws IOException {
        return getByMapStr(WxConstant.GZ_APPSECRET_CACHE_NAME);
    }

    /**
     * 获取网站应用Appid
     * 
     * @return
     * @throws IOException
     */
    public static String getWebAppId() throws IOException {
        return getByMapStr(WxConstant.WEB_APPID_CACHE_NAME);
    }

    /**
     * 获取网站应用Appsecret
     * 
     * @return
     * @throws IOException
     */
    public static String getWebAppsecret() throws IOException {
        return getByMapStr(WxConstant.WEB_APPSECRET_CACHE_NAME);
    }

    /**
     * 获取App应用Appid
     * 
     * @return
     * @throws IOException
     */
    public static String getAppAppId() throws IOException {
        return getByMapStr(WxConstant.APP_APPID_CACHE_NAME);
    }

    /**
     * 获取App应用Appsecret
     * 
     * @return
     * @throws IOException
     */
    public static String getAppAppsecret() throws IOException {
        return getByMapStr(WxConstant.APP_APPSECRET_CACHE_NAME);
    }

    /**
     * 获取小程序应用Appid
     * 
     * @return
     * @throws IOException
     */
    public static String getXcxAppId() throws IOException {
        return getByMapStr(WxConstant.XCX_APPID_CACHE_NAME);
    }

    /**
     * 获取小程序应用Appsecret
     * 
     * @return
     * @throws IOException
     */
    public static String getXcxAppsecret() throws IOException {
        return getByMapStr(WxConstant.XCX_APPSECRET_CACHE_NAME);
    }

    // TODO
    /**
     * 判断一个字符串是否是空(空对象和空字符串)
     * 
     * @param str
     * @return
     */
    private static boolean isBlank(Object str) {
        return StringUtil.isBlank(str);
    }

    /**
     * 判断一个对象是否是空(空对象)
     * 
     * @param obj
     * @return
     */
    private static boolean isBlankObject(Object obj) {
        return StringUtil.isBlankObject(obj);
    }

    /**
     * 判断一个对象是否不是空(空对象)
     * 
     * @param obj
     * @return
     */
    private static boolean isNotBlankObject(Object obj) {
        return StringUtil.isNotBlankObject(obj);
    }

    /**
     * 使用UTF-8编码将字符串转换为application/x-www-form-urlencodedformat
     * 
     * @param url
     * @return
     * @throws IOException
     */
    private static String encodeUrl(String url) {
        return StringUtil.encodeUrl(url);
    }

    /**
     * 生成全局唯一的ID
     * 
     * @return
     */
    private static String generateGuid() {
        return GUIDUtil.generateGuid();
    }

    /**
     * 将对象转换成json字符串(值为空的不解析)
     * 
     * @param data 被转换的对象
     * @return 得到的字符串
     */
    private static String objectToJson(Object data) {
        return JsonUtil.objectToJson(data);
    }

    /**
     * 将json字符串转换成对象
     * 
     * @param jsonData json字符串
     * @param beanType 要转换成的对象的类型
     * @return 得到的对象
     */
    private static WxResponse jsonToObject(String jsonData) {
        WxResponse jsonToObject = JsonUtil.jsonToObject(jsonData, WxResponse.class);
        Status status = new Status();
        jsonToObject.setCusStatus(status);

        String errcode = jsonToObject.getErrcode();
        if (isBlankObject(errcode) || Integer.parseInt(errcode) == 0) {
            status.setCode(TransErrorCode.SUCCESS.getCode());
        } else {
            status.setCode(TransErrorCode.FAIL.getCode());
            status.setMsg(jsonToObject.getErrmsg());
        }
        return jsonToObject;
    }

    /**
     * 将json字符串转换成对象
     * 
     * @param jsonData json字符串
     * @param beanType 要转换成的对象的类型
     * @return 得到的对象
     */
    private static <T> T jsonToObject(String jsonData, Class<T> beanType) {
        return JsonUtil.jsonToObject(jsonData, beanType);
    }

    /**
     * 发送get请求
     * 
     * @param url 请求地址
     * @return
     * @throws Exception 
     */
    private static String doGet(String url) throws Exception {
        return HttpClientUtil.doGet(url);
    }

    /**
     * 发送post请求
     * 
     * @param url 请求地址
     * @param params 参数(Json数据格式)
     * @return
     * @throws IOException 
     */
    private static String doPostByParam(String url, String params) throws IOException {
        return HttpClientUtil.doPostByParam(url, params);
    }

    /**
     * 发送post请求
     * 
     * @param url 请求地址
     * @param params 参数(Json数据格式)
     * @return
     * @throws IOException 
     */
    private static byte[] doPostByteByParam(String url, WxParam wxParam) throws IOException {
        return HttpClientUtil.doPostByteByParam(url, objectToJson(wxParam));
    }

    /**
     * 关闭字节输入流
     * 
     * @param inputStream 需要关闭的字节输入流
     * @throws IOException
     */
    private static void closeStream(InputStream inputStream) throws IOException {
        FileUtil.closeStream(inputStream);
    }

    /**
     * 从缓存中获取值
     * 
     * @param <T>
     * @param key
     * @param requiredType 得到的类型
     * @return
     * @throws IOException
     */
    private static <T> T get(String key, Class<T> requiredType) throws IOException {
        return CacheUtil.get(key, requiredType);
    }

    /**
     * 从缓存中获取值
     * 
     * @param key
     * @return
     * @throws IOException
     */
    private static String getByMapStr(String key) throws IOException {
        return SystemParamBaseUtil.get(key);
    }

    /**
     * 往缓存中存储值,带过期时间(不设置为永久存储,此方法传负数会报错,为了提升性能去掉了if判断,确定不会有负数就调这个方法)
     * 
     * @param key
     * @param value
     * @param timer 过期时间(以秒为单位)
     * @throws IOException
     */
    private static void setexYes(String key, Object value, int timer) throws IOException {
        CacheUtil.setexYes(key, value, timer);
    }

}